#include "Camera.h"


#if DEBUG_VIEWFRUSTUM
vector3 IntersectionPlanes(Plane p1, Plane p2, Plane p3);
#endif

void DecoCamera::set (vector3 Eye, vector3 look, vector3 up)
{
	vector3 n, v, u;
	n.set(Eye.x - look.x, Eye.y - look.y, Eye.z - look.z);
	u = CrossProduct(up, n);

	n.normalize();
	u.normalize();
	v = CrossProduct(n, u);

	backupCoord = coordinate;
	coordinate.Set(u, v, n, Eye);
	matrix44 viewMatrix = LookAtMatrix44(Eye, look, up);

	assert(RI);
	RI->SetTransform(TT_WorldToCamera, viewMatrix);
}
void DecoCamera::roll (FLOAT angle)
{
	coordinate.RotateAlongLocalAxis(PI / 180 * angle, AXIS_Z);

	assert(RI);
	matrix44 worldToCamera = coordinate.GetMatrix().invert();
	RI->SetTransform(TT_WorldToCamera, worldToCamera);
}

void DecoCamera::pitch (FLOAT angle)
{
	coordinate.RotateAlongLocalAxis(PI / 180 * angle, AXIS_X);

	assert(RI);
	RI->SetTransform(TT_WorldToCamera, coordinate.GetMatrix().invert());
}

void DecoCamera::yaw (FLOAT angle)
{
	coordinate.RotateAlongLocalAxis(PI / 180 * angle, AXIS_Y);

	assert(RI);
	RI->SetTransform(TT_WorldToCamera, coordinate.GetMatrix().invert());
}

void DecoCamera::localRotate (FLOAT angle, AXIS axis)
{
	const FLOAT rotationLimit = 80;
	if (axis == AXIS_X)
	{
		vector3 zProj = coordinate.GetYParentProjection();
		zProj.normalize();
		vector3 x, y, z;
		coordinate.GetBase(x, y, z);
		z.normalize();
		DOUBLE cosZ = DotProduct(z, -zProj);
		DOUBLE angleZ = acos(cosZ);
		angleZ = angleZ  * 180 / PI;
		if (angleZ >= rotationLimit && z.y > 0 && angle < 0 || angleZ >= rotationLimit && z.y < 0 && angle > 0 )
			return;

	}
	coordinate.RotateAlongLocalAxis(PI / 180 * angle, axis);

	assert(RI);
	RI->SetTransform(TT_WorldToCamera, coordinate.GetMatrix().invert());
}

void DecoCamera::globalRotate(FLOAT angle, AXIS axis)
{

	coordinate.RotateAlongGlobalAxis(PI / 180 * angle, axis);

	assert(RI);
	RI->SetTransform(TT_WorldToCamera, coordinate.GetMatrix().invert());
}


void DecoCamera::slide (DOUBLE delX, DOUBLE delY, DOUBLE delZ, BOOL bLocal)
{	
	assert(RI);

	const FLOAT slideConstraint = 20;
	if (bLocal)
		coordinate.TranslateLocal(vector3(delX, delY, delZ));
	else
	{
		coordinate.Translate(vector3(delX, delY, delZ));
	}
	matrix44 worldToCamera = coordinate.GetMatrix().invert();
	RI->SetTransform(TT_WorldToCamera, worldToCamera);
}

void DecoCamera::walkFront(FLOAT distance)
{
	if (bIsOrthognal)
	{
		slide(0, distance, 0, TRUE);
	}
	else
	{
		vector3 front = coordinate.GetYParentProjection();
		front *= distance;
		slide(front.x, front.y, front.z);
	}
}

const static DOUBLE LOOK_FROM_SIDE_DISTANCE = 3.0;

void DecoCamera::lookFromSide(AXIS axis, BOOL positiveDir)
{
	coordinate = Coords();
	DOUBLE translateDist = positiveDir ? LOOK_FROM_SIDE_DISTANCE : -LOOK_FROM_SIDE_DISTANCE;
	switch(axis)
	{
	case AXIS_Z:
		{
			if(positiveDir)
				coordinate.RotateAlongGlobalAxis(PI, AXIS_Y);
			coordinate.Translate(vector3(0, 0,-translateDist));
		}
		break;
	case AXIS_Y:
		{
			coordinate.RotateAlongGlobalAxis(PI/2, AXIS_X);
			if(positiveDir)
				coordinate.RotateAlongGlobalAxis(-PI, AXIS_X);
			coordinate.Translate(vector3(0, translateDist, 0));
		}
		break;
	case AXIS_X:
		{
			coordinate.RotateAlongLocalAxis(PI/2, AXIS_Y);
			if(positiveDir)
				coordinate.RotateAlongLocalAxis(PI, AXIS_Y);
			coordinate.Translate(vector3(-translateDist, 0, 0));
		}
	}
	RI->SetTransform(TT_WorldToCamera, coordinate.GetMatrix().invert());
}

const Coords& DecoCamera::getCoords()const
{
	return coordinate;
}

void DecoCamera::walkLeft(FLOAT distance)
{
	if (bIsOrthognal)
	{
		slide(-distance, 0, 0, TRUE);
	}
	else
	{
		vector3 front = coordinate.GetYParentProjection();
		vector3 left = CrossProduct(vector3(0, 1, 0), front);
		left *= distance;
		slide(left.x, left.y, left.z);
	}
}

void DecoCamera::setFrustum (FLOAT vAng, FLOAT asp, FLOAT nearD, FLOAT farD)
{
	bIsOrthognal = FALSE;
	viewAngle = vAng;
	aspect = asp;
	nearDist = nearD;
	farDist = farD;
	DOUBLE e = 1 / (tan(vAng * PI / 360) * asp);

	viewFrustum.NumPlanes = 6;
	viewFrustum.BoundingPlanes[0] = vector4(0, 0, -1, -nearD);
	viewFrustum.BoundingPlanes[1] = vector4(0, 0, 1, farD);
	viewFrustum.BoundingPlanes[2] = vector4(e / sqrt(e * e + 1), 0, -1 / sqrt(e * e + 1), 0);
	viewFrustum.BoundingPlanes[3] = vector4(-e / sqrt(e * e + 1), 0, -1 / sqrt(e * e + 1), 0);
	viewFrustum.BoundingPlanes[4] = vector4(0, e / sqrt(e * e + 1 / (asp * asp)), -1 / (asp * sqrt(e * e + 1 / (asp * asp))), 0);
	viewFrustum.BoundingPlanes[5] = vector4(0, -e / sqrt(e * e + 1 / (asp * asp)), -1 / (asp * sqrt(e * e + 1 / (asp * asp))), 0);
	matrix44 projMatrix = PerspectiveMatrix44(vAng, asp, nearD, farD); 
	assert(RI);
	RI->SetTransform(TT_CameraToScreen, projMatrix);
}

void DecoCamera::setOrtho(FLOAT width, FLOAT height, FLOAT nearD, FLOAT farD)
{
	bIsOrthognal = TRUE;
	
	this->width = width;
	this->height = height;
	nearDist = nearD;
	farDist = farD;

	viewFrustum.NumPlanes = 6;
	viewFrustum.BoundingPlanes[0] = vector4(0, 0, -1, -nearD);
	viewFrustum.BoundingPlanes[1] = vector4(0, 0, 1, farD);
	viewFrustum.BoundingPlanes[2] = vector4(1, 0, 0, width / 2);
	viewFrustum.BoundingPlanes[3] = vector4(-1, 0, 0, width / 2);
	viewFrustum.BoundingPlanes[4] = vector4(0, 1, 0, height / 2);
	viewFrustum.BoundingPlanes[5] = vector4(0, -1, 0, height / 2);
	
	matrix44 projMatrix = OrthoMatrix44(-width / 2, width / 2, -height / 2, height / 2, nearD, farD); 
	assert(RI);
	RI->SetTransform(TT_CameraToScreen, projMatrix);
}

void DecoCamera::toggoleMode()
{
	matrix44 projMatrix;
	if (bIsOrthognal)
	{
		assert(this->viewAngle && this->aspect);
		setFrustum(this->viewAngle, this->aspect, this->nearDist, this->farDist);
		bIsOrthognal = FALSE;
	}
	else
	{
		assert(this->width && this->height);
		setOrtho(this->width, this->height, this->nearDist, this->farDist);
		bIsOrthognal = TRUE;
	}
	assert(RI);
	RI->SetTransform(TT_WorldToCamera, backupCoord.GetMatrix().invert());
	Coords tmpCoord = coordinate;
	coordinate = backupCoord;
	backupCoord = tmpCoord;
}

void DecoCamera::reflectXZPlane()
{
	coordinate.reflectXZPlane();
	matrix44 worldToCamera = coordinate.GetMatrix().invert();
	RI->SetTransform(TT_WorldToCamera, worldToCamera);
}

void DecoCamera::magnify(FLOAT ratio)
{
	if (bIsOrthognal)
		setOrtho(width * ratio, height * ratio, nearDist, farDist);
	else
		setFrustum(viewAngle * ratio, aspect, nearDist, farDist);
}

vector3 DecoCamera::getEye (void)
{
	return coordinate.origin();
}

ConvexVolume DecoCamera::getViewFrustum()
{
#if DEBUG_VIEWFRUSTUM
#include "GL/gl.h"

	ConvexVolume testVolume1 = viewFrustum.TransformBy(coordinate.GetMatrix());
	ConvexVolume testVolume2 = viewFrustum;
	vector3 volumeVertices1[8], volumeVertices2[8];
	volumeVertices1[0] = IntersectionPlanes(testVolume1.BoundingPlanes[0],testVolume1.BoundingPlanes[2], testVolume1.BoundingPlanes[4]);
	volumeVertices1[1] = IntersectionPlanes(testVolume1.BoundingPlanes[0],testVolume1.BoundingPlanes[3], testVolume1.BoundingPlanes[4]);
	volumeVertices1[2] = IntersectionPlanes(testVolume1.BoundingPlanes[0],testVolume1.BoundingPlanes[3], testVolume1.BoundingPlanes[5]);
	volumeVertices1[3] = IntersectionPlanes(testVolume1.BoundingPlanes[0],testVolume1.BoundingPlanes[2], testVolume1.BoundingPlanes[5]);
	volumeVertices1[4] = IntersectionPlanes(testVolume1.BoundingPlanes[1],testVolume1.BoundingPlanes[2], testVolume1.BoundingPlanes[4]);
	volumeVertices1[5] = IntersectionPlanes(testVolume1.BoundingPlanes[1],testVolume1.BoundingPlanes[3], testVolume1.BoundingPlanes[4]);
	volumeVertices1[6] = IntersectionPlanes(testVolume1.BoundingPlanes[1],testVolume1.BoundingPlanes[3], testVolume1.BoundingPlanes[5]);
	volumeVertices1[7] = IntersectionPlanes(testVolume1.BoundingPlanes[1],testVolume1.BoundingPlanes[2], testVolume1.BoundingPlanes[5]);
	
	volumeVertices2[0] = IntersectionPlanes(testVolume2.BoundingPlanes[0],testVolume2.BoundingPlanes[2], testVolume2.BoundingPlanes[4]);
	volumeVertices2[1] = IntersectionPlanes(testVolume2.BoundingPlanes[0],testVolume2.BoundingPlanes[3], testVolume2.BoundingPlanes[4]);
	volumeVertices2[2] = IntersectionPlanes(testVolume2.BoundingPlanes[0],testVolume2.BoundingPlanes[3], testVolume2.BoundingPlanes[5]);
	volumeVertices2[3] = IntersectionPlanes(testVolume2.BoundingPlanes[0],testVolume2.BoundingPlanes[2], testVolume2.BoundingPlanes[5]);
	volumeVertices2[4] = IntersectionPlanes(testVolume2.BoundingPlanes[1],testVolume2.BoundingPlanes[2], testVolume2.BoundingPlanes[4]);
	volumeVertices2[5] = IntersectionPlanes(testVolume2.BoundingPlanes[1],testVolume2.BoundingPlanes[3], testVolume2.BoundingPlanes[4]);
	volumeVertices2[6] = IntersectionPlanes(testVolume2.BoundingPlanes[1],testVolume2.BoundingPlanes[3], testVolume2.BoundingPlanes[5]);
	volumeVertices2[7] = IntersectionPlanes(testVolume2.BoundingPlanes[1],testVolume2.BoundingPlanes[2], testVolume2.BoundingPlanes[5]);

	matrix44 transformMatrix = coordinate.GetMatrix();
	for (size_t i = 0; i < 8; i++)
	{
		vector4 homoPoint(volumeVertices2[i].x, volumeVertices2[i].y, volumeVertices2[i].z, 1.f);
		homoPoint = transformMatrix * homoPoint;
		volumeVertices2[i] = vector3(homoPoint.x, homoPoint.y, homoPoint.z);
	}
	glColor3f(1.f, 0.f, 0.f);
	glBegin(GL_LINE_LOOP);
	for (size_t i = 0; i < 4; i++)
	{
		glVertex3f(volumeVertices1[i].x, volumeVertices1[i].y, volumeVertices1[i].z);
	}
	glEnd();
	glBegin(GL_LINE_LOOP);
	for (size_t i = 4; i < 8; i++)
	{
		glVertex3f(volumeVertices1[i].x, volumeVertices1[i].y, volumeVertices1[i].z);
	}
	glEnd();
	glBegin(GL_LINES);
	for (size_t i = 0; i < 4; i++)
	{
		glVertex3f(volumeVertices1[i].x, volumeVertices1[i].y, volumeVertices1[i].z);
		glVertex3f(volumeVertices1[i + 4].x, volumeVertices1[i + 4].y, volumeVertices1[i + 4].z);

	}
	glEnd();

	glColor3f(0.f, 0.f, 1.f);
	glBegin(GL_LINE_LOOP);
	for (size_t i = 0; i < 4; i++)
	{
		glVertex3f(volumeVertices2[i].x, volumeVertices2[i].y, volumeVertices2[i].z);
	}
	glEnd();
	glBegin(GL_LINE_LOOP);
	for (size_t i = 4; i < 8; i++)
	{
		glVertex3f(volumeVertices2[i].x, volumeVertices2[i].y, volumeVertices2[i].z);
	}
	glEnd();
	glBegin(GL_LINES);
	for (size_t i = 0; i < 4; i++)
	{
		glVertex3f(volumeVertices2[i].x, volumeVertices2[i].y, volumeVertices2[i].z);
		glVertex3f(volumeVertices2[i + 4].x, volumeVertices2[i + 4].y, volumeVertices2[i + 4].z);

	}
	glEnd();

#endif
	return viewFrustum.TransformBy(coordinate.GetMatrix());
}

#if DEBUG_VIEWFRUSTUM
vector3 IntersectionPlanes(Plane p1, Plane p2, Plane p3)
{
	FLOAT m[9];
	m[0] = p1.GetNormal().x;	m[3] = p1.GetNormal().y;	m[6] = p1.GetNormal().z;
	m[1] = p2.GetNormal().x;	m[4] = p2.GetNormal().y;	m[7] = p2.GetNormal().z;
	m[2] = p3.GetNormal().x;	m[5] = p3.GetNormal().y;	m[8] = p3.GetNormal().z;

	vector3 col1(m[0], m[1], m[2]);
	vector3 col2(m[3], m[4], m[5]);
	vector3 col3(m[6], m[7], m[8]);

	matrix33 M(col1, col2, col3);
	vector3 D(-p1.GetOffset(), -p2.GetOffset(), -p3.GetOffset());
	matrix33 invM = M.invert();
	vector3 result = invM * D;
	return result;
}
#endif


DecoArchive& operator<< (DecoArchive& Ar, const DecoCamera& cm)
{
	return Ar << cm.coordinate << cm.backupCoord << cm.viewAngle << cm.aspect << cm.nearDist << cm.farDist << cm.width << cm.height << cm.viewFrustum << cm.bIsOrthognal;
}
DecoArchive& operator>> (DecoArchive& Ar, DecoCamera& cm)
{
	return Ar >> cm.coordinate >> cm.backupCoord >> cm.viewAngle >> cm.aspect >> cm.nearDist >> cm.farDist >> cm.width >> cm.height >> cm.viewFrustum >> cm.bIsOrthognal;
	if (cm.isOrthogonal())
	{
		cm.setFrustum(cm.viewAngle, cm.aspect, cm.nearDist, cm.farDist);
		cm.setOrtho(cm.width, cm.height, cm.nearDist, cm.farDist);
	}
	else
	{		
		cm.setOrtho(cm.width, cm.height, cm.nearDist, cm.farDist);
		cm.setFrustum(cm.viewAngle, cm.aspect, cm.nearDist, cm.farDist);
	}
	cm.RI->SetTransform(TT_WorldToCamera, cm.coordinate.GetMatrix().invert());
}